package wiiusej.apptest.activityTasks;

import java.sql.Timestamp;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Vector;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;

import wiiusej.apptest.statistics.AccelerationPaquet;
import wiiusej.apptest.statistics.performance.ClassTrackName;
import wiiusej.apptest.statistics.performance.Track;
import wiiusej.apptest.statistics.performance.TrackType;

public abstract class HeuristicAlgorithm implements Runnable {

	// Logger
	protected Logger fileLogger;
	private static Logger fileLoggerDelay;
	private static Logger performanceLogger;

	// class name
	private final ClassTrackName className = ClassTrackName.RTF;

	// Event Vector
	private Vector<AccelerationPaquet> myEventVector;

	private static final String fileURL = "src/test/java/wiiusej/apptest/statistics/logs/RealTimeFeedback";
	private static final String fileURLDelay = "src/test/java/wiiusej/apptest/statistics/delay/logs/Acc_to_RT";

	// Speaker Parameters
	private static final int THIRD_LEVEL_FREQ = 1000;
	private static final int FIFTH_LEVEL_FREQ = 250;

	// COM queues
	private BlockingQueue<Vector<AccelerationPaquet>> fromListener;
	private BlockingQueue<Integer> toGameQ;
	private BlockingQueue<Track> toPerformanceController;

	// private BlockingQueue<Integer> toRumbleQ = new
	// LinkedBlockingQueue<Integer>();
	private BlockingQueue<Integer> toSoundQ = new LinkedBlockingQueue<Integer>();

	// FACTORS
	private final double MILLI_FACTOR = 1.0E06;

	// OTHERS
	private String pattern = "000.000";
	DecimalFormat myFormatter = new DecimalFormat(pattern);
	private final int packetLimit = 100;
	private int packetCounter = 0;

	private Timestamp lastPacketTime = null;

	// Constructor that enables lower Software Components connection (i.e.,
	// Sound Controller )
	public HeuristicAlgorithm(BlockingQueue<Vector<AccelerationPaquet>> fromListener,
			BlockingQueue toGameController, BlockingQueue<Integer> toSoundQ,
			BlockingQueue<Track> toPerformanceController) {

		this.toGameQ = toGameController;
		this.fromListener = fromListener;
		this.toPerformanceController = toPerformanceController;
		// this.toRumbleQ = toRumbleQ2;
		this.toSoundQ = toSoundQ;
		
		initLoggers();
	}

	// Constructor that works isolated from lower Software Components. Used for
	// motion recognition
	public HeuristicAlgorithm(BlockingQueue fromListener) {
		this.fromListener = fromListener;		
		initLoggers();
	}
	
	private void initLoggers(){		
		PropertyConfigurator.configure("log4j.properties");
		fileLogger = Logger.getLogger("realTimeFeedback");
		fileLoggerDelay = Logger.getLogger("delayAccToRT");
		performanceLogger = Logger.getLogger("realTimePerformance");

		performanceLogger.info("REAL TIME PERFORMANCE LOGGER");		
		fileLogger.info("REAL TIME PERFORMANCE LOGGER");		
		
	}

	@Override
	public void run() {

		SimpleDateFormat tsFormat = new SimpleDateFormat("mm:ss:SSS");
		fileLogger.info("STARTING RUN!!!");
		
		while (true) {
			
			if(packetCounter >= packetLimit){
				performanceLogger.info("****************************");
				performanceLogger.info("****************************");
				performanceLogger.info("MEASUREMENT HAS FINISHED!!. PacketCounter: " + packetCounter);
				performanceLogger.info("****************************");
				performanceLogger.info("****************************");
				
			}
			fileLogger.info("Listening...");
			try {
				myEventVector = fromListener.take();				
			} catch (InterruptedException e) {
				e.printStackTrace();
			}	
						
			long startN = System.nanoTime();
			
			
			AccelerationPaquet lastPacket = myEventVector.lastElement();
			
			fileLogger.info("");
			fileLogger.info("New call received - MyEventVector: " + myEventVector.size() + 
					" LastElement: " + lastPacket + " Counter: " + lastPacket.getCounter() + 
					" LastPacketTimeStored: " + lastPacketTime);
			
			
			if(lastPacket.getTime()==null){
				fileLogger.info("LAST PACKET NULL TIME!!! WARNING");				
			}else{				
				fileLogger.info("TimeStamp: " + lastPacket.getTime());
			}
			
			if(lastPacketTime == null){
				//first time we get an AccelerationPacket				
				fileLogger.info("First Packet Arrive. Time: " + tsFormat.format(lastPacket.getTime()));
				lastPacketTime = new Timestamp(lastPacket.getTime().getTime());				
				fileLogger.info("LastTimeUploaded: " + tsFormat.format(lastPacketTime.getTime()));
				
				if(toPerformanceController!=null){				
					sendToPerformanceConroller(myEventVector.lastElement().getPacketID(), TrackType.CHUNK);					
				}
				
				
				calculeLastPaquetMean(lastPacket, tsFormat);
				
				//delayStats(startN);
				
				
			}else{				
				
				fileLogger.info("New Packet Arrived: " + tsFormat.format(lastPacket.getTime()) + 
						" Counter: " + lastPacket.getCounter() + 
						" LastPacketManaged: " + tsFormat.format(lastPacketTime.getTime()));
				
				if(myEventVector.lastElement().getTime().getTime() != lastPacketTime.getTime()){
					//new Pacquet added
					fileLogger.info("This is New!!");
					lastPacketTime = new Timestamp(lastPacket.getTime().getTime());
					
					fileLogger.info("Sending to Performance Controller");
					
					//int index = myEventVector.indexOf(myEventVector.lastElement());
					
					if(toPerformanceController!=null){
						sendToPerformanceConroller(myEventVector.lastElement().getPacketID(), TrackType.CHUNK);
					}
					
					
					calculeLastPaquetMean(myEventVector.lastElement(), tsFormat);	
					delayStats(startN);
					packetCounter++;
					
					
					
				}else{					
					fileLogger.info("Packet Already Managed!!");
				}				
			}			
			

		}

	}

	private void sendToPerformanceConroller(long packetID, TrackType trackType) {
		Timestamp time = new Timestamp(System.currentTimeMillis());
		// PerformanceUnit unitReport = new PerformanceUnit(wiimoteID,
		// e.getRefNumber(), time);
		Track track = new Track(className, time, packetID, 1, trackType);

		fileLogger.info("Track: " + track.getPacketID() + " Track: "
				+ track.getTimeIN() + " Type: " + trackType);

		try {
			toPerformanceController.put(track);
		} catch (InterruptedException e1) {
			e1.printStackTrace();
		}

	}

	private void delayStats(long startN) {
		long endN = System.nanoTime();
		performanceLogger.info((endN - startN) / MILLI_FACTOR);
	}

	private int meanAxis(Vector<Integer> axisVector) {

		int mean = 0;
		for (Integer i : axisVector) {

			if (i <= 85) {
				i = 255 - i;
			}

			mean = mean + i;
		}

		return mean / axisVector.size();

	}

	private void printReport(int meanX, int meanY, int meanZ,
			SimpleDateFormat tsFormat, GameModes mode,
			AccelerationPaquet toCheck) {

		fileLogger.debug(" ");
		fileLogger.debug("Paquet: " + tsFormat.format(toCheck.getTime())
				+ " MeanX: " + meanX + " MeanY: " + meanY + " MeanZ: " + meanZ
				+ mode);

	}

	private void calculeLastPaquetMean(AccelerationPaquet nextToLastChecked,
			SimpleDateFormat tsFormat) {
		int meanX, meanY, meanZ;
		if (nextToLastChecked != null) {
			meanX = meanAxis((Vector<Integer>) nextToLastChecked.getAccX()
					.clone());
			meanY = meanAxis((Vector<Integer>) nextToLastChecked.getAccY()
					.clone());
			meanZ = meanAxis((Vector<Integer>) nextToLastChecked.getAccZ()
					.clone());
			//giveFeedback(meanX, meanY, meanZ, tsFormat, nextToLastChecked);
			recognizeMotion(meanX, meanY, meanZ, tsFormat, nextToLastChecked);
		}
	}

	
	protected void recognizeMotion(int meanX, int meanY, int meanZ, SimpleDateFormat tsFormat, AccelerationPaquet nextToLastChecked){
		//This method should be overided in order to perform either just motion recognition (standalone) or motion
		//and send results to Software Components (normal).
		
	}
	
	
	protected GameModes giveFeedback(int meanX, int meanY, int meanZ,
			SimpleDateFormat tsFormat, AccelerationPaquet toCheck) {

		GameModes mode = GameModes.NO_MODE;

		if (meanZ >= 160 || meanX >= 160 || meanY >= 160) {
			mode = GameModes.BIG_SOFT;
		} else if (detectPeaks(toCheck)) {
			mode = GameModes.ROBOTIC;
		} else {
			if (!detectRestPosition(meanX, meanY, meanZ)) {
				mode = GameModes.SLOW_MOTION;				
			} /*else {
				mode = GameModes.REST;				
			}*/
		}

		if (mode != GameModes.NO_MODE) {
			// mode detected
			printReport(meanX, meanY, meanZ, tsFormat, mode, toCheck);
		} else {
			fileLogger.error("MOTION MODE NOT RECOGNIZED!!");
		}

		return mode;

	}

	protected void toSoftwareComponents(GameModes mode) {

		SimpleDateFormat tsFormat = new SimpleDateFormat("mm:ss:SSS");
		
		switch (mode) {

		case SLOW_MOTION:
			
			fileLogger.info(" SLOW MOTION at: "
					+ tsFormat.format(myEventVector.lastElement().getTime()));
			
			try {
				toGameQ.put(GameModes.SLOW_MOTION.ordinal());
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			break;

		case BIG_SOFT:
			fileLogger.info(" BIG SOFT MOTION at: "
					+ tsFormat.format(myEventVector.lastElement().getTime()));
			
			toSoundQ.add(FIFTH_LEVEL_FREQ);
			
			try {
				toGameQ.put(GameModes.BIG_SOFT.ordinal());
			} catch (InterruptedException e) {
				e.printStackTrace();
			}

			break;

		case ROBOTIC:
			fileLogger.info(" ROBOTIC MOTION at: "
					+ tsFormat.format(myEventVector.lastElement().getTime()));
			
			toSoundQ.add(THIRD_LEVEL_FREQ);
			
			try {
				toGameQ.put(GameModes.ROBOTIC.ordinal());
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			break;

			/*
		case REST:			
			fileLogger.info(" REST at: "
					+ tsFormat.format(myEventVector.lastElement().getTime()));
			
			try {
				toGameQ.put(GameModes.REST.ordinal());
				// toRumbleQ.add(1);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			break;*/

		default:
			fileLogger.error("MOTION MODE NOT RECOGNIZED!!");
			break;

		}

	}

	private boolean detectRestPosition(int meanX, int meanY, int meanZ) {

		if (meanX > 140 || meanY > 140 || meanZ > 140) {
			return false;
		}

		if ((meanX > 120 && meanX < 130) || (meanY > 120 && meanY < 130)) {
			return true;
		}

		if ((meanX > 120 && meanX < 130) || (meanZ > 120 && meanZ < 130)) {
			return true;
		}

		if ((meanY > 120 && meanY < 130) || (meanZ > 120 && meanZ < 130)) {
			return true;
		}

		return false;
	}

	private boolean detectPeaks(AccelerationPaquet toCheck) {

		boolean peak = false;

		for (Integer i : toCheck.getAccX()) {
			if (i > 190 || i < 70) {
				peak = true;
			}
		}

		for (Integer i : toCheck.getAccY()) {
			if (i > 190 || i < 70) {
				peak = true;
			}
		}

		for (Integer i : toCheck.getAccZ()) {
			if (i > 190 || i < 70) {
				peak = true;
			}
		}

		return peak;
	}

}
